import React, { useEffect, useMemo, useState } from 'react';
import { getImageData, loadImageData } from 'react2-vtkjs-viewport';
import ConnectedVTKViewport from './ConnectedVTKViewport';
import LoadingIndicator from './LoadingIndicator.js';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types';
import cornerstoneTools from 'cs-cornerstone-tools';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkVolume from 'vtk.js/Sources/Rendering/Core/Volume';
import vtkVolumeMapper from 'vtk.js/Sources/Rendering/Core/VolumeMapper';

const segmentationModule = cornerstoneTools.getModule('segmentation');

const { StackManager, getCornerstoneStack } = OHIF.utils;
let volumeCache = {};
let labelmapCache = {};

const OHIFVTKViewport = props => {
  let segmentsDefaultProperties;
  const { viewportIndex, viewportData: { studies, displaySet }, onScroll = ()=>{}, children } = props;
  const { displaySetInstanceUID } = displaySet;
  const [percentComplete, setPercentComplete] = useState(0);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => () => {
    StackManager.clearStacks();
    volumeCache = {};
    labelmapCache = {};
  }, []);

  const dataDetails = useMemo(() => {
    const study = studies.find(
      study => study.StudyInstanceUID === displaySet.StudyInstanceUID
    );

    return {
      studyDate: study.StudyDate,
      studyTime: study.StudyTime,
      studyDescription: study.StudyDescription,
      patientName: study.PatientName,
      patientId: study.PatientID,
      patientSex: study.PatientSex,
      patientBirthDate: study.PatientBirthDate,
      patientAge: study.PatientAge,
      modality: displaySet.Modality,
      seriesNumber: String(displaySet.SeriesNumber),
      seriesDescription: displaySet.SeriesDescription,
    };
  }, [studies, displaySet]);

  const stack = useMemo(() => getCornerstoneStack(studies, displaySet, true), [studies, displaySet]);

  const imageData = useMemo(() => {
    const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
    let labelmapDataObject;
    let labelmapColorLUT;

    const firstImageId = stack.imageIds[0];
    const { state } = segmentationModule;
    const brushStackState = state.series[firstImageId];

    if (brushStackState) {
      const { activeLabelmapIndex } = brushStackState;
      const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];

      segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
        isHidden => {
          return { visible: !isHidden };
        }
      );

      const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;

      if (labelmapCache[vtkLabelmapID]) {
        labelmapDataObject = labelmapCache[vtkLabelmapID];
      } else {
        // TODO -> We need an imageId based getter in cornerstoneTools
        const labelmapBuffer = labelmap3D.buffer;

        // Create VTK Image Data with buffer as input
        labelmapDataObject = vtkImageData.newInstance();

        const dataArray = vtkDataArray.newInstance({
          numberOfComponents: 1, // labelmap with single component
          values: new Uint16Array(labelmapBuffer),
        });

        labelmapDataObject.getPointData().setScalars(dataArray);
        labelmapDataObject.setDimensions(...imageDataObject.dimensions);
        labelmapDataObject.setSpacing(
          ...imageDataObject.vtkImageData.getSpacing()
        );
        labelmapDataObject.setOrigin(
          ...imageDataObject.vtkImageData.getOrigin()
        );
        labelmapDataObject.setDirection(
          ...imageDataObject.vtkImageData.getDirection()
        );

        // Cache the labelmap volume.
        labelmapCache[vtkLabelmapID] = labelmapDataObject;
      }

      labelmapColorLUT = state.colorLutTables[labelmap3D.colorLUTIndex];
    }

    return {
      imageDataObject,
      labelmapDataObject,
      labelmapColorLUT,
    };
  }, [stack]);

  const volumeData = useMemo(() => {
    if (volumeCache[displaySetInstanceUID]) {
      return volumeCache[displaySetInstanceUID];
    }

    const { vtkImageData, imageMetaData0 } = imageData.imageDataObject;
    // TODO -> Should update react2-vtkjs-viewport and react-cornerstone-viewports
    // internals to use naturalized DICOM JSON names.
    const {
      windowWidth: WindowWidth,
      windowCenter: WindowCenter,
      modality: Modality,
    } = imageMetaData0;

    const { lower, upper } = _getRangeFromWindowLevels(
      WindowWidth,
      WindowCenter,
      Modality
    );
    const volumeActor = vtkVolume.newInstance();
    const volumeMapper = vtkVolumeMapper.newInstance();

    volumeActor.setMapper(volumeMapper);
    volumeMapper.setInputData(vtkImageData);

    volumeActor
      .getProperty()
      .getRGBTransferFunction(0)
      .setRange(lower, upper);

    const spacing = vtkImageData.getSpacing();
    const sampleDistance = (spacing[0] + spacing[1] + spacing[2]) / 6;

    volumeMapper.setSampleDistance(sampleDistance);

    // Be generous to surpress warnings, as the logging really hurts performance.
    // TODO: maybe we should auto adjust samples to 1000.
    volumeMapper.setMaximumSamplesPerRay(4000);

    volumeCache[displaySetInstanceUID] = volumeActor;

    return volumeActor;
  }, [imageData]);

  const loadProgressively = () => {
    loadImageData(imageData.imageDataObject);

    const { isLoading, imageIds } = imageData.imageDataObject;

    if (!isLoading) {
      setIsLoaded(true);
      return;
    }

    const NumberOfFrames = imageIds.length;

    const onPixelDataInsertedCallback = numberProcessed => {
      const percent = Math.floor(
        (numberProcessed * 100) / NumberOfFrames
      );

      if (percent !== percentComplete) {
        setPercentComplete(percent);
      }
    };

    const onPixelDataInsertedErrorCallback = error => {
      throw new Error("MPR Load Error");
    };

    const onAllPixelDataInsertedCallback = () => {
      setIsLoaded(true);
    };

    imageData.imageDataObject.onPixelDataInserted(onPixelDataInsertedCallback);
    imageData.imageDataObject.onAllPixelDataInserted(onAllPixelDataInsertedCallback);
    imageData.imageDataObject.onPixelDataInsertedError(onPixelDataInsertedErrorCallback);
  };

  useEffect(loadProgressively, []);

  let childrenWithProps = null;
  const { configuration } = segmentationModule;
  // TODO: Does it make more sense to use Context?
  if (children && children.length) {
    childrenWithProps = children.map((child, index) => {
      return (
        child &&
        React.cloneElement(child, {
          viewportIndex,
          key: index,
        })
      );
    });
  }
  const style = { width: '100%', height: '100%', position: 'relative' };
  return (
    <>
      <div style={style}>
        {!isLoaded && (
          <LoadingIndicator percentComplete={percentComplete} />
        )}
        {volumeData && (
          <ConnectedVTKViewport
            volumes={[volumeData]}
            paintFilterLabelMapImageData={
              imageData.labelmapDataObject
            }
            paintFilterBackgroundImageData={
              imageData.imageDataObject.vtkImageData
            }
            viewportIndex={viewportIndex}
            dataDetails={dataDetails}
            labelmapRenderingOptions={{
              colorLUT: imageData.labelmapColorLUT,
              globalOpacity: configuration.fillAlpha,
              visible: configuration.renderFill,
              outlineThickness: configuration.outlineWidth,
              renderOutline: configuration.renderOutline,
              segmentsDefaultProperties,
            }}
            onScroll={onScroll}
          />
        )}
      </div>
      {childrenWithProps}
    </>
  );
}

/**
 * Takes window levels and converts them to a range (lower/upper)
 * for use with VTK RGBTransferFunction
 *
 * @private
 * @param {number} [width] - the width of our window
 * @param {number} [center] - the center of our window
 * @param {string} [Modality] - 'PT', 'CT', etc.
 * @returns { lower, upper } - range
 */
function _getRangeFromWindowLevels(width, center, Modality = undefined) {
  // For PET just set the range to 0-5 SUV
  if (Modality === 'PT') {
    return { lower: 0, upper: 5 };
  }

  const levelsAreNotNumbers = isNaN(center) || isNaN(width);

  if (levelsAreNotNumbers) {
    return { lower: 0, upper: 512 };
  }

  return {
    lower: center - width / 2.0,
    upper: center + width / 2.0,
  };
}

OHIFVTKViewport.propTypes = {
  viewportData: PropTypes.shape({
    studies: PropTypes.array.isRequired,
    displaySet: PropTypes.shape({
      StudyInstanceUID: PropTypes.string.isRequired,
      displaySetInstanceUID: PropTypes.string.isRequired,
      sopClassUIDs: PropTypes.arrayOf(PropTypes.string),
      SOPInstanceUID: PropTypes.string,
      frameIndex: PropTypes.number,
    }),
  }),
  viewportIndex: PropTypes.number.isRequired,
  children: PropTypes.node,
  onScroll: PropTypes.func,
};

export default OHIFVTKViewport;
